/*
 * Copyright (C) 2006 Erik Swenson - erik@oreports.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */

package org.efs.openreports.engine;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.sf.jasperreports.engine.JRAbstractExporter;
import net.sf.jasperreports.engine.JREmptyDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JRField;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JRReport;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.design.JRDesignBand;
import net.sf.jasperreports.engine.design.JRDesignExpression;
import net.sf.jasperreports.engine.design.JRDesignField;
import net.sf.jasperreports.engine.design.JRDesignParameter;
import net.sf.jasperreports.engine.design.JRDesignQuery;
import net.sf.jasperreports.engine.design.JRDesignStaticText;
import net.sf.jasperreports.engine.design.JRDesignTextField;
import net.sf.jasperreports.engine.design.JasperDesign;
import net.sf.jasperreports.engine.export.JExcelApiExporter;
import net.sf.jasperreports.engine.export.JRCsvExporter;
import net.sf.jasperreports.engine.export.JRHtmlExporter;
import net.sf.jasperreports.engine.export.JRHtmlExporterParameter;
import net.sf.jasperreports.engine.export.JRPdfExporter;
import net.sf.jasperreports.engine.export.JRRtfExporter;
import net.sf.jasperreports.engine.export.JRTextExporter;
import net.sf.jasperreports.engine.export.JRTextExporterParameter;
import net.sf.jasperreports.engine.export.JRXlsAbstractExporterParameter;
import net.sf.jasperreports.engine.export.JRXlsExporter;
import net.sf.jasperreports.engine.query.JRXPathQueryExecuterFactory;
import net.sf.jasperreports.engine.util.JRLoader;
import net.sf.jasperreports.engine.util.JRQueryExecuter;
import net.sf.jasperreports.engine.util.JRXmlUtils;

import org.apache.commons.beanutils.DynaProperty;
import org.apache.commons.beanutils.RowSetDynaClass;
import org.apache.log4j.Logger;
import org.efs.openreports.ReportConstants.ExportType;
import org.efs.openreports.engine.input.ReportEngineInput;
import org.efs.openreports.engine.output.JasperReportEngineOutput;
import org.efs.openreports.engine.output.ReportEngineOutput;
import org.efs.openreports.objects.Report;
import org.efs.openreports.objects.ReportDataSource;
import org.efs.openreports.objects.ReportExportOption;
import org.efs.openreports.objects.ReportParameter;
import org.efs.openreports.objects.ReportParameterMap;
import org.efs.openreports.providers.DataSourceProvider;
import org.efs.openreports.providers.DirectoryProvider;
import org.efs.openreports.providers.PropertiesProvider;
import org.efs.openreports.providers.ProviderException;
import org.efs.openreports.util.LocalStrings;
import org.efs.openreports.util.ORUtil;
import org.w3c.dom.Document;

/**
 * JasperReports ReportEngine implementation. Report generation is separated
 * into fillReport and exportReport methods in order to provide direct access
 * to the JasperPrint object when required.
 *
 * @author Erik Swenson
 *
 */
public class JasperReportEngine extends ReportEngine
{
    protected static Logger log = Logger.getLogger(JasperReportEngine.class.getName());

    public JasperReportEngine(DataSourceProvider dataSourceProvider,
            DirectoryProvider directoryProvider, PropertiesProvider propertiesProvider)
    {
        super(dataSourceProvider,directoryProvider, propertiesProvider);
    }

    @Override
    public ReportEngineOutput generateReport(ReportEngineInput input)
            throws ProviderException
    {
        JasperPrint jasperPrint = fillReport(input);

        ReportEngineOutput engineOutput = exportReport(jasperPrint,
                input.getExportType(), input.getReport().getReportExportOption(), input
                        .getImagesMap(), input.isInlineImages());

        return engineOutput;
    }

    public JasperPrint fillReport(ReportEngineInput input) throws ProviderException
    {
        Connection conn = null;

        Report report = input.getReport();
        Map<String,Object> parameters = input.getParameters();

        ReportDataSource dataSource = report.getDataSource();

        try
        {
            JasperReport jr = null;

            if (report.isQueryReport()) return fillQueryReport(report, parameters, input.getExportType());

            jr = (JasperReport) JRLoader
                    .loadObject(directoryProvider.getReportDirectory() + report.getFile());

            List<ReportParameterMap> subReports = report.getSubReportParameters();
            if (subReports != null && subReports.size() > 0)
            {
                Iterator<ReportParameterMap> iterator = report.getSubReportParameters().iterator();
                while (iterator.hasNext())
                {
                    ReportParameterMap rpMap = iterator.next();

                    JasperReport subReport = (JasperReport) JRLoader.loadObject(directoryProvider
                            .getReportDirectory()
                            + rpMap.getReportParameter().getData());

                    parameters.put(rpMap.getReportParameter().getName(), subReport);
                }
            }

            JasperPrint jp = null;

            // create new HashMap to send to JasperReports in order to
            // fix serialization problems
            Map<String,Object> jasperReportMap = new HashMap<String,Object>(parameters);

            if (input.getXmlInput() != null)
            {
                ByteArrayInputStream stream = new ByteArrayInputStream(input.getXmlInput().getBytes());
                Document document = JRXmlUtils.parse(stream);

                jasperReportMap.put(JRXPathQueryExecuterFactory.PARAMETER_XML_DATA_DOCUMENT, document);

                jp = JasperFillManager.fillReport(jr, jasperReportMap);
            }
            else if (dataSource == null)
            {
                jp = JasperFillManager.fillReport(jr, jasperReportMap, new JREmptyDataSource());
            }
            else
            {
                conn = dataSourceProvider.getConnection(dataSource.getId());
                jp = JasperFillManager.fillReport(jr, jasperReportMap, conn);
            }

            if (jp == null || jp.getPages().size() < 1) throw new ProviderException(LocalStrings.ERROR_REPORT_EMPTY);

            return jp;
        }
        catch (Exception e)
        {
            if (!e.getMessage().equals(LocalStrings.ERROR_REPORT_EMPTY)) log.error("JasperReportEngine.fillReport", e);
            throw new ProviderException(e.getMessage());
        }
        finally
        {
            try
            {
                if (conn != null) conn.close();
            }
            catch (Exception ex)
            {
                log.error("Error closing connection: " + ex.getMessage());
            }
        }
    }

    public ReportEngineOutput exportReport(JasperPrint jasperPrint, ExportType exportType,
            ReportExportOption exportOptions, Map<?,?> imagesMap, boolean inlineImages) throws ProviderException
    {
        JasperReportEngineOutput engineOutput = new JasperReportEngineOutput();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        JRAbstractExporter exporter = null;

        try
        {
            if (exportType == ExportType.PDF)
            {
                engineOutput.setContentType(ReportEngineOutput.CONTENT_TYPE_PDF);

                exporter = new JRPdfExporter();
            }
            else if (exportType == ExportType.XLS
                    || exportType == ExportType.EXCEL)
            {
                engineOutput.setContentType(ReportEngineOutput.CONTENT_TYPE_XLS);

                if (exportType == ExportType.XLS)
                {
                    exporter = new JRXlsExporter();
                }
                else if (exportType == ExportType.EXCEL)
                {
                    exporter = new JExcelApiExporter();
                }

                exporter.setParameter(
                        JRXlsAbstractExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS,
                        new Boolean(exportOptions.isXlsRemoveEmptySpaceBetweenRows()));

                exporter.setParameter(JRXlsAbstractExporterParameter.IS_ONE_PAGE_PER_SHEET,
                        new Boolean(exportOptions.isXlsOnePagePerSheet()));

                exporter.setParameter(JRXlsAbstractExporterParameter.IS_DETECT_CELL_TYPE,
                        new Boolean(exportOptions.isXlsAutoDetectCellType()));

                exporter.setParameter(JRXlsAbstractExporterParameter.IS_WHITE_PAGE_BACKGROUND,
                        new Boolean(exportOptions.isXlsWhitePageBackground()));
            }
            else if (exportType == ExportType.CSV)
            {
                engineOutput.setContentType(ReportEngineOutput.CONTENT_TYPE_CSV);

                exporter = new JRCsvExporter();
            }
            else if (exportType == ExportType.TEXT)
            {
                engineOutput.setContentType(ReportEngineOutput.CONTENT_TYPE_TEXT);

                exporter = new JRTextExporter();
                exporter.setParameter(JRTextExporterParameter.CHARACTER_WIDTH,
                        new Integer(10));
                exporter.setParameter(JRTextExporterParameter.CHARACTER_HEIGHT,
                        new Integer(10));
            }
            else if (exportType == ExportType.RTF)
            {
                engineOutput.setContentType(ReportEngineOutput.CONTENT_TYPE_RTF);

                exporter = new JRRtfExporter();
            }
            else
            {
                engineOutput.setContentType(ReportEngineOutput.CONTENT_TYPE_HTML);

                exporter = new JRHtmlExporter();

                exporter.setParameter(
                        JRHtmlExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS,
                        new Boolean(exportOptions.isHtmlRemoveEmptySpaceBetweenRows()));

                exporter.setParameter(JRHtmlExporterParameter.IS_USING_IMAGES_TO_ALIGN,
                        new Boolean(exportOptions.isHtmlUsingImagesToAlign()));

                exporter.setParameter(JRHtmlExporterParameter.IS_WHITE_PAGE_BACKGROUND,
                        new Boolean(exportOptions.isHtmlWhitePageBackground()));

                exporter.setParameter(JRHtmlExporterParameter.IS_WRAP_BREAK_WORD,
                        new Boolean(exportOptions.isHtmlWrapBreakWord()));

                if (imagesMap == null) imagesMap = new HashMap<Object,Object>();
                exporter.setParameter(JRHtmlExporterParameter.IMAGES_MAP, imagesMap);

                if (inlineImages)
                {
                    exporter.setParameter(JRHtmlExporterParameter.IMAGES_URI, "cid:");
                }
                else
                {
                    //see ImageLoaderAction for more information
                    exporter.setParameter(JRHtmlExporterParameter.IMAGES_URI,
                        "imageLoader.action?imageName=");
                }

                //if embedded html, just include <div> tags instead of <html>,<header>, <body>, etc.
                if (exportType == ExportType.HTML_EMBEDDED)
                {
                    exporter.setParameter(JRHtmlExporterParameter.HTML_HEADER, "<div>");
                    exporter.setParameter(JRHtmlExporterParameter.HTML_FOOTER, "</div>");
                }
            }

            exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
            exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, outputStream);
            exporter.exportReport();
        }
        catch (Exception e)
        {
            throw new ProviderException(e.toString());
        }

        engineOutput.setImagesMap(imagesMap);
        engineOutput.setContent(outputStream.toByteArray());

        return engineOutput;
    }

    /*
     * Creates a default JasperPrint from a QueryReport. This method is used when
     * a scheduled QueryReport is executed.
     */
    private JasperPrint fillQueryReport(Report report, Map<String,Object> map, ExportType exportType) throws Exception
    {
        Connection conn = null;
        PreparedStatement pStmt = null;
        ResultSet rs = null;

        // create new HashMap to send to JasperReports in order to
        // fix serialization problems
        Map<String,Object> parameters = new HashMap<String,Object>(map);

        List<?> results = null;
        DynaProperty[] properties = null;

        try
        {
            ReportDataSource dataSource = report.getDataSource();
            conn = dataSourceProvider.getConnection(dataSource.getId());

            if (parameters == null || parameters.isEmpty())
            {
                pStmt = conn.prepareStatement(report.getQuery());
            }
            else
            {
                // Use JasperReports Query logic to parse parameters in chart
                // queries

                JRDesignQuery query = new JRDesignQuery();
                query.setText(report.getQuery());

                // convert parameters to JRDesignParameters so they can be
                // parsed
                Map<String, JRDesignParameter> jrParameters = ORUtil.buildJRDesignParameters(parameters);

                pStmt = JRQueryExecuter.getStatement(query, jrParameters, parameters, conn);
            }

            rs = pStmt.executeQuery();

            RowSetDynaClass rowSetDynaClass = new RowSetDynaClass(rs);

            results = rowSetDynaClass.getRows();
            properties = rowSetDynaClass.getDynaProperties();

            rs.close();
        }
        catch (Exception e)
        {
            throw new ProviderException("Error executing report query: " + e.getMessage());
        }
        finally
        {
            try
            {
                if (pStmt != null) pStmt.close();
                if (conn != null) conn.close();
            }
            catch (Exception c)
            {
                log.error("Error closing");
            }
        }

        JasperDesign jasperDesign = new JasperDesign();
        jasperDesign.setName(report.getName().replaceAll(" ", "_"));

        int width = jasperDesign.getPageWidth();
        int height = jasperDesign.getPageHeight();

        jasperDesign.setOrientation(JRReport.ORIENTATION_LANDSCAPE);
        jasperDesign.setPageHeight(width);
        jasperDesign.setPageWidth(height);

        for (int i = 0; i < properties.length; i++)
        {

            JRDesignField field = new JRDesignField();
            field.setName(properties[i].getName());
            field.setValueClass(properties[i].getType());

            try
            {
                jasperDesign.addField(field);
            }
            catch (Exception e)
            {
                log.warn(e);
            }
        }

        if (exportType == ExportType.PDF)
        {
            // add title
            JRDesignStaticText sText = new JRDesignStaticText();
            sText.setX(0);
            sText.setY(0);
            sText.setWidth(jasperDesign.getPageHeight());
            sText.setHeight(50);
            sText.setText(jasperDesign.getName());
            sText.setFontSize(16);
            sText.setBold(true);

            JRDesignBand band = new JRDesignBand();
            band.setHeight(50);
            band.addElement(sText);

            jasperDesign.setTitle(band);

            // add page footer for page numbers
            band = new JRDesignBand();
            band.setHeight(15);

            sText = new JRDesignStaticText();
            sText.setX(0);
            sText.setY(0);
            sText.setHeight(15);
            sText.setWidth(40);
            sText.setText("Page:");

            band.addElement(sText);

            JRDesignExpression exp = new JRDesignExpression();
            exp.addVariableChunk("PAGE_NUMBER");
            exp.setValueClass(Integer.class);

            JRDesignTextField txt = new JRDesignTextField();
            txt.setExpression(exp);
            txt.setX(40);
            txt.setY(0);
            txt.setHeight(15);
            txt.setWidth(100);

            band.addElement(txt);

            jasperDesign.setPageFooter(band);
        }

        JRDesignBand emptyBand = new JRDesignBand();
        emptyBand.setHeight(0);
        jasperDesign.setPageHeader(emptyBand);
        jasperDesign.setColumnFooter(emptyBand);
        jasperDesign.setSummary(emptyBand);

        JRField[] fields = jasperDesign.getFields();

        // add column header and detail bands
        JRDesignBand bandDetail = new JRDesignBand();
        bandDetail.setHeight(20);

        JRDesignBand bandHeader = new JRDesignBand();
        bandHeader.setHeight(20);

        int fieldWidth = (jasperDesign.getPageWidth() - jasperDesign.getLeftMargin()
                - jasperDesign.getRightMargin() - (fields.length - 1) * jasperDesign.getColumnSpacing())
                / fields.length;

        for (int i = 0; i < fields.length; i++)
        {
            try
            {
                JRField field = fields[i];

                JRDesignExpression exp = new JRDesignExpression();
                exp.addFieldChunk(field.getName());

                if (field.getValueClassName().equals("java.sql.Date"))
                {
                    // JasperReports does not support java.sql.Date in text field expression
                    exp.setValueClass(java.util.Date.class);
                }
                else
                {
                    exp.setValueClass(field.getValueClass());
                }

                JRDesignTextField txt = new JRDesignTextField();
                txt.setExpression(exp);
                txt.setX(i * fieldWidth);
                txt.setY(0);
                txt.setHeight(20);
                txt.setWidth(fieldWidth);

                if (field.getValueClass().equals(Double.class))
                {
                    txt.setPattern("0.00");
                }

                bandDetail.addElement(txt);

                JRDesignStaticText sText = new JRDesignStaticText();
                sText.setX(i * fieldWidth);
                sText.setY(0);
                sText.setHeight(20);
                sText.setWidth(fieldWidth);
                sText.setText(field.getName());
                sText.setUnderline(true);

                bandHeader.addElement(sText);
            }
            catch (Exception e)
            {
                log.warn(e);
            }
        }

        if (exportType == ExportType.PDF) jasperDesign.setColumnHeader(bandHeader);
        jasperDesign.setDetail(bandDetail);

        JasperReport jasperReport = JasperCompileManager.compileReport(jasperDesign);
        JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters,
                new JRBeanCollectionDataSource(results));

        return jasperPrint;
    }

    @Override
    public List<ReportParameter> buildParameterList(Report report) throws ProviderException
    {
        try
        {
            JasperReport jasperReport = (JasperReport) JRLoader
            .loadObject(directoryProvider.getReportDirectory() + report.getFile());

            ArrayList<ReportParameter> parameters = new ArrayList<ReportParameter>();

            JRParameter[] jrParameters = jasperReport.getParameters();
            for (int i=0; i < jrParameters.length; i++)
            {
                if (!jrParameters[i].isSystemDefined())
                {
                    ReportParameter rp = new ReportParameter();
                    rp.setClassName(jrParameters[i].getValueClassName());
                    rp.setDescription(jrParameters[i].getName());
                    rp.setName(jrParameters[i].getName());
                    rp.setType(ReportParameter.TEXT_PARAM);

                    parameters.add(rp);
                }
            }

            return parameters;
        }
        catch(JRException e)
        {
            throw new ProviderException(e);
        }
    }
}